﻿/******************************************************************************
* SunnyUI.Localization 是基于.Net框架的C# Winform国际化多语言切换解决方案
* CopyRight (C) 2012-2021 ShenYongHua(沈永华).
* QQ群：56829229 QQ：17612584 EMail：SunnyUI@QQ.Com
*
* Blog:  https://www.cnblogs.com/yhuse
* Gitee: https://gitee.com/yhuse/SunnyUI.Localization
* Nuget: https://www.nuget.org/packages/SunnyUI.Localization/
*
* SunnyUI.Localization can be used for free under the MIT license.
* If you use this code, please keep this note.
* 如果您使用此代码，请保留此说明。
******************************************************************************
* 文件名称: UFormGenerator.cs
* 文件说明: 语言资源文件生成器
* 当前版本: V1.0
* 创建日期: 2020-01-01
*
* 2020-09-09: V1.0.2 增加文件说明
******************************************************************************/

using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Windows.Forms;
using System.Xml;

namespace SunnyUI.Localization
{
    public static class FormGenerator
    {
        public static readonly List<string> DisallowSuffixes = new();

        private static readonly Dictionary<string, List<LanguageItem>> LanguageInfos = new();

        private static readonly List<string> csprojFiles = new();

        private static string ExeFile;

        public static string LanguageDir { get; set; } = "Languages";

        public static string LanguageFile(this CultureInfo culture)
        {
            return Path.Combine(Application.StartupPath, LanguageDir, culture.Name + ".lang");
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="csprojFile">项目文件（*.csproj）</param>
        /// <param name="exeFile">可执行文件（*.exe）</param>
        public static void Load(string csprojFile, string exeFile)
        {
            ExeFile = exeFile;
            LanguageInfos.Clear();
            csprojFiles.Clear();
            csprojFiles.Add(csprojFile);
            csprojFiles.Add(csprojFile + ".user");
            Reload();
        }

        private static void Reload()
        {
            List<string> files = new List<string>();
            foreach (var file in csprojFiles)
            {
                if (File.Exists(file))
                {
                    files.AddRange(LoadCSProj(file));
                }
            }

            ReadSourceFiles(files);
        }

        private static List<string> LoadCSProj(string csprojFile)
        {
            List<string> files = new List<string>();
            XmlDocument xml = new XmlDocument();
            xml.Load(csprojFile);
            if (xml.DocumentElement == null)
            {
                return files;
            }

            foreach (XmlNode group in xml.DocumentElement.ChildNodes)
            {
                if (group.Name != "ItemGroup") continue;

                foreach (XmlNode node in group.ChildNodes)
                {
                    if (node.Name != "Compile") continue;
                    if (node.Attributes == null) continue;
                    if (node.Attributes["Include"] == null) continue;

                    string fileName = Path.Combine(Path.GetDirectoryName(csprojFile) ?? string.Empty, node.Attributes["Include"].InnerText);

                    if (fileName.EndsWith("Designer.cs") && (fileName.IndexOf(@"\Properties\", StringComparison.Ordinal) < 0))
                    {
                        files.Add(fileName);
                    }
                    else if (fileName.IndexOf("Messages", StringComparison.Ordinal) >= 0)
                    {
                        files.Add(fileName);
                    }
                    else if (fileName.IndexOf(@"\Properties\", StringComparison.Ordinal) < 0)
                    {
                        files.Add(fileName);
                    }
                }
            }

            return files;
        }

        private static void ReadSourceFiles(List<string> files)
        {
            LanguageInfos.Clear();
            foreach (var file in files)
            {
                ReadSourceFile(file);
            }
        }

        private static void ReadClassName(string fileName, string line, string prefix, ref string className)
        {
            if (className == "")
                className = line.Substring(prefix.Length).Trim();
            else
                throw new Exception($"Expected one class in file {fileName}");
        }

        private static void ReadString(string fileName, string[] lines, ref int pos, string prefix, string className, bool isStatic)
        {
            bool csharp = Path.GetExtension(fileName).Equals(".cs", StringComparison.CurrentCultureIgnoreCase);
            string line = lines[pos].Trim();
            line = line.Substring(prefix.Length).Trim();
            if (csharp && line.EndsWith("\";"))
                line = line.Substring(0, line.Length - 2).Trim();
            else if (csharp && line.EndsWith("\" +"))
            {
                line = line.Substring(0, line.Length - "\" +".Length).Trim();
                pos++;
                while (pos < lines.Length && lines[pos].Trim().EndsWith("\";") == false)
                {
                    string addLine = lines[pos].Trim();
                    if (addLine.StartsWith("\"") == false)
                        throw new Exception($"Expected start \" in file {fileName}, pos {pos}");
                    addLine = addLine.Substring(1).Trim();
                    if (addLine.EndsWith("\" +") == false)
                        throw new Exception($"Expected end \" + in file {fileName}, pos {pos}");
                    addLine = addLine.Substring(0, addLine.Length - "\" +".Length).Trim();
                    line += addLine;
                    pos++;
                }
                string lastLine = lines[pos].Trim();
                if (lastLine.StartsWith("\"") == false)
                    throw new Exception($"Expected start \" in file {fileName}, pos {pos}");
                lastLine = lastLine.Substring(1).Trim();
                if (lastLine.EndsWith("\";") == false)
                    throw new Exception($"Expected end \"; in file {fileName}, pos {pos}");
                lastLine = lastLine.Substring(0, lastLine.Length - "\";".Length).Trim();
                line += lastLine;
            }
            else if (csharp == false && line.EndsWith("\""))
                line = line.Substring(0, line.Length - 1).Trim();
            else if (csharp == false && line.EndsWith("\" +"))
            {
                line = line.Substring(0, line.Length - "\" +".Length).Trim();
                pos++;
                while (pos < lines.Length && lines[pos].Trim().EndsWith("\"") == false)
                {
                    string addLine = lines[pos].Trim();
                    if (addLine.StartsWith("\"") == false)
                        throw new Exception($"Expected start '\"' in file {fileName}, pos {pos}");
                    addLine = addLine.Substring(1).Trim();
                    if (addLine.EndsWith("\" +") == false)
                        throw new Exception($"Expected end '\" +' in file {fileName}, pos {pos}");
                    addLine = addLine.Substring(0, addLine.Length - "\" +".Length).Trim();
                    line += addLine;
                    pos++;
                }
                string lastLine = lines[pos].Trim();
                if (lastLine.StartsWith("\"") == false)
                    throw new Exception($"Expected start '\"' in file {fileName}, pos {pos}");
                lastLine = lastLine.Substring(1).Trim();
                if (lastLine.EndsWith("\"") == false)
                    throw new Exception($"Expected end '\"' in file {fileName}, pos {pos}");
                lastLine = lastLine.Substring(0, lastLine.Length - "\"".Length).Trim();
                line += lastLine;
            }
            int equ = line.IndexOf(" = \"", StringComparison.CurrentCultureIgnoreCase);
            if (equ < 0)
                throw new Exception($"Expected ' = \"' in file {fileName}, pos {pos}");
            string property = line.Substring(0, equ);
            if (csharp == false)
                property = property.Replace(" As String", "");
            string message = line.Substring(equ + 4);

            bool isDisallow = (property == "Name") || (property.EndsWith(".Name") && property.StartsWith(message));
            foreach (string disallow in DisallowSuffixes)
                if (property.EndsWith(disallow))
                    isDisallow = true;

            if (isDisallow == false && className != "")
            {
                LanguageItem item = new LanguageItem();
                item.Class = className;
                item.Property = property;
                item.Value = message;
                item.Static = isStatic;

                if (!LanguageInfos.ContainsKey(item.Class))
                {
                    LanguageInfos.Add(item.Class, new List<LanguageItem>());
                }

                LanguageInfos[item.Class].Add(item);
                Console.WriteLine(" >> " + item);
            }
        }

        private static void ReadSourceFile(string fileName)
        {
            string[] lines = File.ReadAllLines(fileName);
            bool csharp = string.Compare(Path.GetExtension(fileName), ".cs", StringComparison.OrdinalIgnoreCase) == 0;
            string className = "";
            int pos = 0;
            while (pos < lines.Length)
            {
                string line = lines[pos].Trim();
                if (csharp)
                {
                    if (line.StartsWith("partial class "))
                        ReadClassName(fileName, line, "partial class ", ref className);
                    else if (line.StartsWith("class "))
                        ReadClassName(fileName, line, "class ", ref className);
                    else if (line.StartsWith("public class "))
                        ReadClassName(fileName, line, "public class ", ref className);
                    else if (line.StartsWith("public static class "))
                        ReadClassName(fileName, line, "public static class ", ref className);
                    else if (line.StartsWith("public static string ") && line.IndexOf("= \"", StringComparison.CurrentCultureIgnoreCase) > 0)
                        ReadString(fileName, lines, ref pos, "public static string ", className, true);
                    else if (line.StartsWith("public string ") && line.IndexOf("= \"", StringComparison.CurrentCultureIgnoreCase) > 0)
                        ReadString(fileName, lines, ref pos, "public string ", className, false);
                    else if (line.StartsWith("this.") && line.IndexOf(" = \"", StringComparison.CurrentCultureIgnoreCase) > 0)
                        ReadString(fileName, lines, ref pos, "this.", className, false);
                    else if (line.IndexOf(" Text = \"", StringComparison.CurrentCultureIgnoreCase) > 0)
                        ReadString(fileName, lines, ref pos, "this.", className, false);
                }
                else
                {
                    if (line.StartsWith("Partial Class "))
                        ReadClassName(fileName, line, "Partial Class ", ref className);
                    else if (line.StartsWith("Class "))
                        ReadClassName(fileName, line, "Class ", ref className);
                    else if (line.StartsWith("Public Class "))
                        ReadClassName(fileName, line, "Public Class ", ref className);
                    else if (line.StartsWith("Public Shared ") && line.IndexOf("= \"", StringComparison.CurrentCultureIgnoreCase) > 0)
                        ReadString(fileName, lines, ref pos, "Public Shared ", className, true);
                    else if (line.StartsWith("Public ") && line.IndexOf("= \"", StringComparison.CurrentCultureIgnoreCase) > 0)
                        ReadString(fileName, lines, ref pos, "Public ", className, false);
                    else if (line.StartsWith("Me.") && line.IndexOf(" = \"", StringComparison.CurrentCultureIgnoreCase) > 0)
                        ReadString(fileName, lines, ref pos, "Me.", className, false);
                    else if (line.IndexOf(" Text = \"", StringComparison.CurrentCultureIgnoreCase) > 0)
                        ReadString(fileName, lines, ref pos, "this.", className, false);
                }
                pos++;
            }
        }

        public static string Save()
        {
            if (!File.Exists(ExeFile)) return string.Empty;
            var path = new FileInfo(ExeFile).DirectoryName;
            if (path == null) return string.Empty;

            if (Directory.Exists(path))
            {
                string dir = Path.Combine(path, LanguageDir);
                if (!Directory.Exists(dir))
                {
                    Directory.CreateDirectory(dir);
                }

                IniFile ini = new IniFile(Path.Combine(dir, "local.lang"));
                ini.Write("Language", "Name", "local");
                ini.Write("Language", "ID", "0");
                ini.Write("Language", "CHInfo", "此文件不要做任何修改，重新生成会覆盖此文件。");
                ini.Write("Language", "ENInfo", "Do not make any changes to this file, it will be overwritten by regenerating it.");

                foreach (List<LanguageItem> infos in LanguageInfos.Values)
                {
                    foreach (var info in infos)
                    {
                        ini.Write(info.Class, info.Property, info.Value);
                    }
                }

                ini.UpdateFile();
                return ini.FileName;
            }

            return string.Empty;
        }

        public static void Save(string neutralFile, CultureInfo culture)
        {
            if (string.IsNullOrEmpty(neutralFile))
            {
                return;
            }

            IniFile ini = new IniFile(neutralFile);
            string outfile = culture.LanguageFile();
            string[] sections = ini.Sections;

            bool exists = File.Exists(outfile);
            IniFile iniFile = new IniFile(outfile);
            foreach (var section in sections)
            {
                foreach (var key in ini.GetKeys(section))
                {
                    if (section == "Language")
                    {
                        if (key.Equals("CHInfo", StringComparison.CurrentCultureIgnoreCase)) continue;
                        if (key.Equals("ENInfo", StringComparison.CurrentCultureIgnoreCase)) continue;
                    }

                    if (!exists || !iniFile.KeyExists(section, key))
                    {
                        iniFile.Write(section, key, ini.Read(section, key, ""));
                    }
                }
            }

            iniFile.Write("Language", "Name", culture.Name);
            iniFile.Write("Language", "ID", culture.LCID.ToString());
            iniFile.Write("Language", "NativeName", culture.NativeName);
            iniFile.Write("Language", "DisplayName", culture.DisplayName);
            iniFile.Write("Language", "EnglishName", culture.EnglishName);
            iniFile.UpdateFile();
        }
    }
}
